
	/********************************************************************************

		@API
		Module: Timer

		Timers:
			tid			create				(action, interval, data*)
			mod			remove				(sel*)
			mod			data					(tid, data*)

		Groups:
			mod			createGroup		(id, parent*)
			mod			removeGroup		(id)

		Query:
			tids		select				(sel*)
			n				count					(sel*)

		Commands:
			mod			run						(sel*)
			mod			stop					(sel*)
			mod			pause					(sel*)
			mod			enable				(sel*)
			mod			disable				(sel*)

		Status:
			b				exists				(tid)
			b				isRunning			(tid)
			b				isPaused			(tid)
			b				isIdle				(tid)
			b				isEnabled			(tid)
			s				status				(tid)
			n				waiting				(tid)

		Events:
			beforeCreate, beforeRemove	,	 beforeStart	, beforeStop	, beforeResume,
			create			, remove				,	 start				,	stop				, resume

			beforePause , beforeExecute	,	 beforeEnable	, beforeDisable,
			pause				, execute				,	 enable				, disable

		Legend:
			sel				tid | g:group | filter
			data			{ action*, interval*, tid, group, object, param, enabled }

			adata			{ i, time, tl, param }
			action		object.action(adata)

		Private

			__validateData	(data)
			__validateSel		(sel)

			__select				(sel)
			__run						(data)
			__stop					(data)
			__pause					(data)

	*******************************************************************************/

	//-------------------------------------------------------------------------------
	jsl.Timer = (function() {
		try {
			var mod

			mod 					= new jsl.Module("jsl.Timer")
			mod.__tid			= 1
			mod.__timers	= []
			mod.__groups	= {}

			mod.events("beforeCreate"	, "beforeRemove"	, "beforeStart"	, "beforeStop"	, "beforeResume")
			mod.events("create"				, "remove"				, "start"				, "stop"				, "resume")

			mod.events("beforePause"	, "beforeExecute"	, "beforeAbort"	, "beforeEnable", "beforeDisable")
			mod.events("pause"				, "execute"				, "abort"				, "enable"			, "disable")

			return mod
		}
		catch(e) {
			jsl.throwError(e, "jsl.Timer")
		}
	})()
	//-------------------------------------------------------------------------------
	jsl.Timer.create = function(action, interval, data) {
		try {
			var tdata, ns, e

			//!
			if(arguments.length < 2 || arguments.length > 3)	throw jsl.ParamError()
			jsl.validate(action		, "f"		, "action")
			jsl.validate(interval	, "i++"	, "interval")
			if(arguments.length === 3)	this.__validateData(data, true)//!

			if(data && data.tid && data.tid.charAt(0) === "T" && data.tid.slice(1) >= 0)
				this.__tid = +data.tid.slice(1) + 1

			//params
			tdata						= {}
			tdata.action 		= action
			tdata.interval 	= +interval
			tdata.tid				= data && data.tid							? data.tid		+ "" 				: "T" + this.__tid++
			tdata.group			= data && data.group 						? data.group 	+ "" 				: null
			tdata.enabled		= data && data.enabled 	!= null	? data.enabled.valueOf() 	: true
			tdata.param			= data && "param" in data				? data.param 							: null
			tdata.object		= data && data.object || {}

			//data
			tdata.nid				= null
			tdata.ts				= null
			tdata.status 		= "idle"
			tdata.tick			= 0

			ns = tdata.tid + "."
			this.events(	ns + "beforeRemove"	, ns + "beforeStart"		, ns + "beforeStop"		, ns + "beforeResume"	,
										ns + "remove"				,	ns + "start"					, ns + "stop"					, ns + "resume"				,
										ns + "beforePause"	, ns + "beforeExecute"	, ns + "beforeAbort"	, ns + "beforeEnable"	, ns + "beforeDisable",
										ns + "pause"				, ns + "execute"				, ns + "abort"				, ns + "enable"				, ns + "disable")


			e 			= new jsl.Event
			e.timer = jsl.Object.clone(tdata)
			this.throwEvent("beforeCreate", e)

			this.__timers.push(tdata)
			this.__timers["." + tdata.tid] = tdata

			this.throwEvent("create", e)
			return tdata.tid
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.remove = function(sel) {
		try {
			var ts, e, tid, ns

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				e 			= new jsl.Event
				e.timer	= jsl.Object.clone(ts[a])
				tid			= e.timer.tid
				ns			= tid + "."

				this.throwEvent(ns + "beforeRemove", e)
				this.throwEvent("beforeRemove", e)

				//no stop event, if you want a stop event you can add a listener
				//to the beforeAbort event
				if(ts[a].status !== "idle")	{

					this.throwEvent(ns + "beforeAbort"	, e)
					this.throwEvent("beforeAbort"			, e)

					this.__stop(ts[a])
					e.status = ts[a].status

					this.throwEvent(ns + "abort", e)
					this.throwEvent("abort"			, e)
				}

				delete this.__timers["." + tid]

				for(var b = 0, l2 = this.__timers.length; b < l2; b++)
					if(ts[a] === this.__timers[b]) {
						this.__timers.splice(b, 1)
						break
					}

				this.throwEvent(ns + "remove", e)
				this.throwEvent("remove", e)

				this.removeEvents(ns + "beforeRemove"	, ns + "beforeStart"		, ns + "beforeStop"		, ns + "beforeResume"	,
													ns + "remove"				, ns + "start"					, ns + "stop"					, ns + "resume"				,
													ns + "beforePause"	, ns + "beforeExecute"	, ns + "beforeAbort"	, ns + "beforeEnable"	, ns + "beforeDisable",
													ns + "pause"				, ns + "execute"				, ns + "abort"				, ns + "enable"				, ns + "disable")
			}

			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.data = function(tid, data) {
		try {
			var tdata, ns, v, t, obj

			//!
			if(arguments.length !== 1 && arguments.length !== 2)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")
			if(arguments.length === 2)		this.__validateData(data)//!

			if(arguments.length === 1) {
				t	= this.__timers["." + tid]

				obj 					= {}
				obj.tid				= t.tid
				obj.action		= t.action
				obj.group			= t.group
				obj.interval	= t.interval
				obj.enabled		= t.enabled
				obj.param			= t.param
				obj.object		= t.object

				obj.status		= t.status
				obj.tick			= t.tick

				v = obj
			}
			else {
				tdata	= this.__timers["." + tid]
				v = this

				if(data.tid)							tdata.tid 			= data.tid 		+ ""
				if(data.group)						tdata.group 		= data.group 	+ ""
				if(data.action)						tdata.action 		= data.action
				if(data.interval)					tdata.interval	= +data.interval
				if(data.enabled != null)	tdata.enabled 	= data.enabled.valueOf()

				if("object"		in data)		tdata.object 		= data.object || {}
				if("param" 		in data)		tdata.param 		= data.param

				if(tid != tdata.tid) {

					delete this.__timers["." + tid]
					this.__timers["." + tdata.tid] = tdata

					ns = tid + "."
					this.removeEvents(ns + "beforeRemove"	, ns + "beforeStart"		, ns + "beforeStop"		, ns + "beforeResume"	,
														ns + "remove"				, ns + "start"					, ns + "stop"					, ns + "resume"				,
														ns + "beforePause"	, ns + "beforeExecute"	, ns + "beforeAbort"	, ns + "beforeEnable"	, ns + "beforeDisable",
														ns + "pause"				, ns + "execute"				, ns + "abort"				, ns + "enable"				, ns + "disable")

					ns = tdata.tid + "."
					this.events(			ns + "beforeRemove"	, ns + "beforeStart"		, ns + "beforeStop"		, ns + "beforeResume"	,
														ns + "remove"				, ns + "start"					, ns + "stop"					, ns + "resume"				,
														ns + "beforePause"	, ns + "beforeExecute"	, ns + "beforeAbort"	, ns + "beforeEnable"	, ns + "beforeDisable",
														ns + "pause"				, ns + "execute"				, ns + "abort"				, ns + "enable"				, ns + "disable")
				}
			}

			return v
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.createGroup = function(group, parent) {
		try {
			var g

			//!
			if(!arguments.length || arguments.length > 2)	throw jsl.ParamError()
			jsl.validate(group, "s+", "group")
			if(this.__groups[group])	throw jsl.ParamError("AlreadyDefined:group")

			if(arguments.length === 2) {
				jsl.validate(parent, "s+", "parent")
				if(!this.__groups[parent])	throw jsl.ParamError("NotDefined:parent")
			}//!

			g 										= { id: group, cs: [] }
			this.__groups[group] 	= g

			if(parent) {
				this.__groups[parent].cs.push(g)
				g.parent = this.__groups[parent]
			}

			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.removeGroup = function(group) {
		try {
			var g, cs, p

			//!
			if(arguments.length !== 1) throw jsl.ParamError()
			jsl.validate(group, "s+", "group")//!

			g = this.__groups[group]

			if(g) {

				cs = g.cs

				while(cs[0])
					this.removeGroup(cs[0].id)

				this.remove("g:" + group)

				p = g.parent

				if(p) {

					cs = p.cs

					for(var a = 0, l = cs.length; a < l; a++)
						if(g === cs[a]) {
							cs.splice(a, 1)
							break
						}
				}

				delete this.__groups[group]
			}

			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.select = function(sel) {
		try {
			var ts, rv//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)
			rv = []

			for(var a = 0, l = ts.length; a < l; a++)
				rv[a] = ts[a].tid

			return rv
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.count = function(sel) {
		try {//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			return this.__select(sel).length
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.run = function(sel) {
		try {
			var ts, timer, e, tid

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				timer	= ts[a]

				if(timer.enabled && timer.status !== "run") {

					e 			= new jsl.Event
					e.timer	= jsl.Object.clone(timer)
					tid			= e.timer.tid

					if(timer.status === "idle") {

						this.throwEvent(tid + ".beforeStart", e)
						this.throwEvent("beforeStart", e)

						timer.ts = new Date
						this.__run(timer)

						this.throwEvent(tid + ".start"	, e)
						this.throwEvent("start", e)
					}
					else {

						this.throwEvent(tid + ".beforeResume", e)
						this.throwEvent("beforeResume", e)

						this.__run(timer)

						this.throwEvent(tid + ".resume", e)
						this.throwEvent("resume", e)
					}
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.stop = function(sel) {
		try {
			var ts, e, timer, tid

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				timer	= ts[a]

				if(timer.enabled && timer.status !== "idle") {

					e 			= new jsl.Event
					e.timer	= jsl.Object.clone(timer)
					tid			= e.timer.tid

					this.throwEvent(tid + ".beforeStop", e)
					this.throwEvent("beforeStop", e)

					this.__stop(timer)

					this.throwEvent(tid + ".stop", e)
					this.throwEvent("stop", e)
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.pause = function(sel) {
		try {
			var ts, e, timer, tid

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				timer	= ts[a]

				if(timer.enabled && timer.status !== "pause") {

					e 			= new jsl.Event
					e.timer	= jsl.Object.clone(timer)
					tid			= e.timer.tid

					if(timer.status === "idle")
						this.__run(timer)

					this.throwEvent(tid + ".beforePause", e)
					this.throwEvent("beforePause", e)

					this.__pause(timer)

					this.throwEvent(tid + ".pause", e)
					this.throwEvent("pause", e)
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.enable = function(sel) {
		try {
			var ts, e, timer, tid

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				timer	= ts[a]

				if(!timer.enabled) {

					e 			= new jsl.Event
					e.timer	= jsl.Object.clone(timer)
					tid			= e.timer.tid

					this.throwEvent(tid + ".beforeEnable", e)
					this.throwEvent("beforeEnable", e)

					timer.enabled 	= true
					e.timer.enabled = true

					this.throwEvent(tid + ".enable", e)
					this.throwEvent("enable", e)
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.disable = function(sel) {
		try {
			var ts, e, timer, tid

			//!
			if(arguments.length > 1)	throw jsl.ParamError()
			if(arguments.length)			this.__validateSel(sel)//!

			ts = this.__select(sel)

			for(var a = 0, l = ts.length; a < l; a++) {

				timer	= ts[a]

				if(timer.enabled) {

					e 			= new jsl.Event
					e.timer	= jsl.Object.clone(timer)
					tid			= e.timer.tid

					this.throwEvent(tid + ".beforeDisable", e)
					this.throwEvent("beforeDisable", e)

					timer.enabled 	= false
					e.timer.enabled = false

					this.throwEvent(tid + ".disable", e)
					this.throwEvent("disable", e)
				}
			}
			return this
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.exists = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")//!

			return !!this.__timers["." + tid]
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.isRunning = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			return this.__timers["." + tid].status === "run"
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.isPaused = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			return this.__timers["." + tid].status === "pause"
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.isIdle = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			return this.__timers["." + tid].status === "idle"
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.isEnabled = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			return this.__timers["." + tid].enabled
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.status = function(tid) {
		try {
			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			return this.__timers["." + tid].status
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.waiting = function(tid) {
		try {
			var t, w

			//!
			if(arguments.length !== 1)	throw jsl.ParamError()
			jsl.validate(tid, "s+", "tid")
			if(!this.__timers["." + tid])	throw jsl.ParamError("NotDefined:tid")//!

			t = this.__timers["." + tid]

			if(t.status !== "run")
				w = Infinity
			else  {
				w = t.ts - new Date + t.interval * t.tick
				w = w <= 0 ? 0 : w
			}

			return w
		}
		catch(e) {
			jsl.throwError(e, this, arguments)
		}
	}
	//-------------------------------------------------------------------------------
	// private
	//-------------------------------------------------------------------------------
	//!
	//validators
	//-------------------------------------------------------------------------------
	jsl.Timer.__validateSel = function(sel) {
		jsl.validate(sel, "t[s+,f]", "sel")

		if(jsl.isString(sel))
			if(sel.slice(0, 2) === "g:") {
				if(!this.__groups[sel.slice(2)])	throw jsl.ParamError("NotDefined:sel(group)")
			}
			else if(!this.__timers["." + sel])	throw jsl.ParamError("NotDefined:sel(tid)")
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.__validateData = function(data, strict) {

		jsl.validate(data, "o", "data")

		if(data.tid 			!= null)	jsl.validate(data.tid			, "s+"	, "data.tid")
		if(data.group			!= null)	jsl.validate(data.group		, "s+"	, "data.group")
		if(data.enabled 	!= null)	jsl.validate(data.enabled	, "b"		, "data.enabled")
		if(data.object		!= null)	jsl.validate(data.object	, "o"		, "data.object")

		if(!strict)
			if(data.action 				!= null)	jsl.validate(data.action	, "f"		, "data.action")
			else if(data.interval	!= null)	jsl.validate(data.interval, "i++"	, "data.interval")

		if(data.tid 	&&  this.__timers["." + data.tid])	throw jsl.ParamError("AlreadyDefined:data.tid")
		if(data.group && !this.__groups[data.group])			throw jsl.ParamError("NotDefined:data.group")
	}
	//!
	//-------------------------------------------------------------------------------
	jsl.Timer.__run = function(timer) {
		var dt

		timer.status 	= "run"
		dt 						= timer.ts - new Date + timer.interval * timer.tick
		dt 						= dt <= 0 ? 0 : dt

		timer.nid	= jsl.delay(function() {
			var e, ns, Timer

			if(timer.enabled) {

				e 			= new jsl.Event
				e.timer	= jsl.Object.clone(timer)
				ns			= timer.tid + "."
				Timer		= jsl.Timer

				Timer.throwEvent(ns + "beforeExecute", e)
				Timer.throwEvent("beforeExecute", e)

				try 			{ timer.action.call(timer.object, e.timer) 			}
				catch(e) 	{ throw jsl.ScriptError("BadScript:action", e) 	}

				Timer.throwEvent(ns + "execute", e)
				Timer.throwEvent("execute", e)
			}

			timer.tick++

			if(timer.status === "run")
				jsl.Timer.__run(timer)
		}, dt)
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.__pause = function(timer) {
		timer.nid && jsl.removeTimer(timer.nid)
		timer.status 	= "pause"
		timer.nid 		= null
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.__stop = function(timer) {
		timer.nid && jsl.removeTimer(timer.nid)
		timer.status 	= "idle"
		timer.nid 		= null
		timer.tick 		= 1
	}
	//-------------------------------------------------------------------------------
	jsl.Timer.__select = function(sel) {
		var rv, ts, l, a, Obj, g, cs

		rv 			= []
		ts 			= this.__timers
		l				= ts.length
		Obj			= jsl.Object

		//1-all
		if(sel == null)
			rv = Obj.clone(ts)

		//2-filter
		else if(jsl.isFunction(sel))
			for(a = 0; a < l; a++)
				try 			{ if(sel(Obj.clone(ts[a]))) rv.push(ts[a]) 				}
				catch(e) 	{ throw jsl.ParamError("BadScript:sel(data)", e) 	}

		else if(jsl.isString(sel))

			//3-group
			if(sel.slice(0, 2) === "g:") {

				g = this.__groups[sel.slice(2)]

				if(g) {
					cs = g.cs

					for(a = 0; a < l; a++) if(ts[a].group === g.id) rv.push(ts[a])
					for(a = 0; a < cs.length; a++)									rv 	= rv.concat(this.__select("g:" + cs[a].id))
				}
			}
			//4-tid
			else if(this.__timers["." + sel])
				rv.push(this.__timers["." + sel])

		return rv
	}
